package com.jungle.bfi.generator;

import com.jungle.bfi.generator.config.Config;
import com.jungle.bfi.generator.config.InputCfg;
import com.jungle.bfi.generator.config.OutputCfg;
import com.jungle.bfi.generator.config.TemplateCfg;
import com.jungle.bfi.generator.util.PathUtil;
import com.jungle.bfi.generator.mo.ModelMo;
import com.jungle.bfi.generator.mo.AttributeMo;
import com.jungle.bfi.generator.mo.PathInfo;
import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.StringTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.MapModel;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModelException;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
public class Generator {
    public static void main(String[] args) throws IOException {
        try {
            // 获取配置文件路径
            Path projectPath = PathUtil.getProjectPath();
            Path resourcesPath = projectPath.resolve("src/main/resources");
            Path configFilePath = resourcesPath.resolve("GeneratorConfig.yml");
            // 持久类
            String fullClassName = "com.jungle.bfi.entity.VoucherTypePo";
            // 持久类所属模块
            String module = "voucher";

            // 生成代码
            generateCode(configFilePath.toFile(), fullClassName, module);
        } catch (IOException e) {
            log.error("load config error:", e);
        } catch (TemplateModelException e) {
            log.error("set sharedVar error:", e);
        }
    }

    public static void generateCode(File configFile, String fullClassName, String module) throws IOException, TemplateModelException {

        Config config = ConfigLoader.load(configFile);

        List<TemplateCfg> templates = config.getGenerator().getTemplates();
        if (templates == null || templates.isEmpty()) {
            log.error("未配置模板信息");
            return;
        }
        ModelMo modelMo = ModelLoader.load(fullClassName);
        modelMo.setModule(module);
        BiConsumer<OutputCfg, Template> consumerTemplate = modelsConsumerTemplate(Collections.singletonList(modelMo));
        TemplateBuilder templateBuilder = new TemplateBuilder("jungle");
        templates.forEach(templateCfg -> {
            try {
                Template template = getTemplate(templateCfg, templateBuilder);

                consumerTemplate.accept(templateCfg.getOutput(), template);
            } catch (Exception e) {
                log.error("模板配置错误", e);
            }
        });
    }

    private static BiConsumer<OutputCfg, Template> modelsConsumerTemplate(List<ModelMo> list) {
        return (outputCfg, template) -> list.forEach(modelMo -> {
            try {
                consumerTemplate(outputCfg, modelMo, template);
            } catch (TemplateException | IOException e) {
                log.error("使用模板错误", e);
            }
        });
    }


    private static Template getTemplate(TemplateCfg templateCfg, TemplateBuilder templateBuilder) throws Exception {
        InputCfg inputCfg = templateCfg.getInput();
        String templateDir = inputCfg.getTemplateDir();
        String templateName = inputCfg.getTemplateName();
        // 读取模板
        return templateBuilder.build(new File(templateDir), templateName);
    }

    private static void consumerTemplate(OutputCfg outputCfg, ModelMo modelMo, Template template) throws TemplateException, IOException {
        String packageName = buildPackageName(outputCfg, modelMo);
        modelMo.setPackageName(packageName);

        String packagePath = packageName.replace(".", "/");

        Path basePath = outputCfg.getBasePath();
        Path javaPath = basePath.resolve(packagePath)
                .resolve(modelMo.getModel())
                .resolve(outputCfg.getSuffix());
        File outputFile = javaPath.toFile();
        // 校验确保目录存在
        if (outputFile.getParentFile().exists() || outputFile.getParentFile().mkdirs()) {
            template.process(modelMo, new FileWriter(outputFile, outputCfg.charset()));
        }
    }

    private static String buildPackageName(OutputCfg outputCfg, ModelMo modelMo) {
        if (modelMo.getModule() == null) {
            return outputCfg.getBasePackage() + "." + outputCfg.getSubPackage();
        } else {
            return outputCfg.getBasePackage() + "." + outputCfg.getSubPackage().replace("${module}", modelMo.getModule());
        }
    }

    public static void main1(String[] args) throws IOException {
        Path javaFilePath = Paths.get("com/jungle/bfi/entity/VoucherTypePo.java");
        ModelMo model = getModel(javaFilePath);
        String poClass = PathUtil.getFileMainName(javaFilePath);
        try {
            new Generator().generateCode(model);
            log.info("{} 相关代码成功生成!", poClass);
        } catch (TemplateModelException e) {
            log.error(poClass.concat(" 相关代码生成失败!"), e);
        }
    }

    private static ModelMo getModel(Path path) throws IOException {

        // 转换为Java源文件路径
        Path projectPath = getProjectPath(path);
        assert projectPath != null;
        log.debug("projectPath:\t{}", projectPath);


        String className = PathUtil.getFileMainName(path);
        // 读取Java源文件
        JavaClass javaClass = readJavaSource(projectPath, className);

        // 转换为类信息

        // 设置项目路径
        //modelMo.setProjectDir(projectPath.getParent());

        return toClassInfo(javaClass);
    }

    private static Path getProjectPath(Path path) {
        int index = PathUtil.indexOf(path, Path.of("target", "classes"));
        if (index < 0) {
            index = PathUtil.indexOf(path, Path.of("target", "test-classes"));
        }
        if (index < 0) {
            return null;
        }
        return path.getRoot().resolve(path.subpath(0, index));
    }

    private static ModelMo toClassInfo(JavaClass javaClass) {
        ModelMo modelMo = new ModelMo();
        modelMo.setName(javaClass.getName());
        modelMo.setModel(javaClass.getName().replace("Po", ""));
        modelMo.setComment(javaClass.getComment());
        modelMo.setModule(getBizModule(javaClass.getComment()));

        List<AttributeMo> list = javaClass.getFields().stream().map(field -> {
            AttributeMo attributeMo = new AttributeMo();
            attributeMo.setName(field.getName());
            attributeMo.setComment(field.getComment());
            attributeMo.setType(field.getType().getName());
            return attributeMo;
        }).toList();

        modelMo.setFields(list);
        return modelMo;
    }

    Configuration cfg = null;

    public void generateCode(ModelMo dataModel) throws TemplateModelException {
        cfg = new Configuration(Configuration.VERSION_2_3_32);
        // 设置模板文件位置
        cfg.setTemplateLoader(new ClassTemplateLoader(Generator.class, "/template"));
        // 设置编码
        cfg.setDefaultEncoding("UTF-8");
        // 设置全局变量
        Map<String, Object> map = new HashMap<>();
        map.put("author", "jungle");
        map.put("since", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        MapModel mapModel = new MapModel(map, new BeansWrapper(Configuration.VERSION_2_3_32));
        cfg.setAllSharedVariables(mapModel);

        try {
            String domain = "domain";
            generateCode(dataModel, "Bo.java.ftl", domain);
            generateCode(dataModel, "Vo.java.ftl", domain);
            generateCode(dataModel, "Qo.java.ftl", domain);
            String main = "main";
            generateCode(dataModel, "Repository.java.ftl", main);
            generateCode(dataModel, "Service.java.ftl", main);
            generateCode(dataModel, "ServiceImpl.java.ftl", main);
            generateCode(dataModel, "Controller.java.ftl", main);
        } catch (IOException | TemplateException e) {
            log.error("通过模板生成代码失败", e);
        }
    }

    private void generateCode(ModelMo model, String templateFileName, String projectModule) throws IOException, TemplateException {
        Template template = cfg.getTemplate(templateFileName);

        PathInfo targetInfo = null;//model.buildTargetInfo();
        targetInfo.setBaseDir("src/main/java");
        targetInfo.setExtension(template.getName().replace(".ftl", ""));

        targetInfo.setProjectModule(projectModule);
        // 获取模板源代码
        String source = template.getRootTreeNode().getSource();
        // 从模板中获取包名
        String packageName = getPackageName(source, model);

        targetInfo.setPackageName(packageName);

        try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(targetInfo.getJavaPath().toFile()), StandardCharsets.UTF_8)) {
            template.process(model, out);
        }
    }

    private String getPackageName(String javaCode, ModelMo model) throws IOException {
        String packageNameTemplate = getPackageNameTemplate(javaCode);
        if (packageNameTemplate != null) {
            StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
            //暂存原始的模板加载器
            TemplateLoader templateLoader = cfg.getTemplateLoader();
            cfg.setTemplateLoader(stringTemplateLoader);

            String templateName = "packageNameTemplate";
            stringTemplateLoader.putTemplate(templateName, packageNameTemplate);

            Template template = cfg.getTemplate(templateName);
            try {
                StringWriter writer = new StringWriter();
                template.process(model, writer);
                return writer.toString();
            } catch (TemplateException e) {
                log.error("通过模板生成代码失败", e);
            } finally {
                stringTemplateLoader.removeTemplate(templateName);
                // 还原始的模板加载器
                cfg.setTemplateLoader(templateLoader);
            }
        }
        return null;
    }

    private static String getPackageNameTemplate(String javaCode) {
        return getByRegex(javaCode, "^\\s*package\\s+([a-zA-Z0-9${}_.]+)\\s*;", Pattern.MULTILINE);
    }

    private static String getBizModule(String classComment) {
        return getByRegex(classComment, "\\[(.*?)\\]", Pattern.DOTALL);
    }

    private static String getByRegex(String strContent, String regex, int flags) {
        Pattern pattern = Pattern.compile(regex, flags);
        Matcher matcher = pattern.matcher(strContent);
        return matcher.find() ? matcher.group(1) : null;
    }

    private static JavaClass readJavaSource(Path javaDir, String className) throws IOException {
        JavaProjectBuilder builder = new JavaProjectBuilder();

        // 添加Java源文件
        Path javaFile = javaDir.resolve(Path.of("src", "main", "java")).resolve(className.replace(".", "/").concat(".java"));
        builder.addSource(javaFile.toFile());

        // 获取类信息
        return builder.getClassByName(className);
    }
}
